package cc.shacocloud.mirage.utils.converter.support;

import cc.shacocloud.mirage.utils.ClassUtil;
import cc.shacocloud.mirage.utils.converter.ConversionException;
import cc.shacocloud.mirage.utils.converter.ConversionSupport;
import cc.shacocloud.mirage.utils.converter.TypeDescriptor;
import cc.shacocloud.mirage.utils.map.ConcurrentReferenceHashMap;
import cc.shacocloud.mirage.utils.map.MapUtil;
import cc.shacocloud.mirage.utils.reflection.ModifierUtil;
import cc.shacocloud.mirage.utils.reflection.ReflectUtil;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 转换为 {@link Enum} 类型的转换器
 *
 * @author 思追(shaco)
 */
public class ToEnumConversion extends AbstractConversion implements ConversionSupport {
    
    private static final Map<Class<?>, Map<Class<?>, Method>> VALUE_METHOD_CACHE = new ConcurrentReferenceHashMap<>(256);
    
    @Override
    public boolean support(@NotNull TypeDescriptor sourceType, @NotNull TypeDescriptor targetType) {
        Class<?> objectType = targetType.getObjectType();
        return objectType.isEnum();
    }
    
    @SuppressWarnings({"unchecked", "rawtypes"})
    @Override
    public Object convert(@NotNull Object source, @NotNull TypeDescriptor sourceType, @NotNull TypeDescriptor targetType) throws ConversionException {
        if (source instanceof Enum) {
            return source;
        }
        
        Class<?> objectType = targetType.getObjectType();
        Class<?> sourceObjectType = sourceType.getObjectType();
        Enum anEnum = tryEnum(source, sourceObjectType, (Class<? extends Enum>) objectType);
        
        if (Objects.nonNull(anEnum)) {
            return anEnum;
        }
        
        throw new ConversionException(sourceType, targetType, source);
    }
    
    /**
     * 尝试找到类似转换的静态方法调用实现转换且优先使用 <br>
     * 1. 约定枚举类应该提供 valueOf(String) 和 valueOf(Integer)用于转换
     * 2. name 转换托底
     *
     * @param value     被转换的值
     * @param enumClass enum类
     * @return 对应的枚举值
     */
    @SuppressWarnings("unchecked")
    protected <T extends Enum<T>> Enum<T> tryEnum(@NotNull Object value, Class<?> sourceType, Class<T> enumClass) {
        // EnumItem实现转换
        Enum<T> enumResult = null;
        
        // 用户自定义方法
        // 查找枚举中所有返回值为目标枚举对象的方法，如果发现方法参数匹配，就执行之
        final Map<Class<?>, Method> methodMap = getValueOfMethodMap(enumClass);
        if (MapUtil.isNotEmpty(methodMap)) {
            for (Map.Entry<Class<?>, Method> entry : methodMap.entrySet()) {
                if (ClassUtil.isAssignable(entry.getKey(), sourceType)) {
                    enumResult = (Enum<T>) ReflectUtil.invokeStatic(entry.getValue(), value);
                }
            }
        }
        
        if (null == enumResult) {
            enumResult = Enum.valueOf(enumClass, convertToStr(value));
        }
        return enumResult;
    }
    
    /**
     * 获取枚举的静态转换方法
     *
     * @param enumClass 枚举类
     * @return 转换方法map，key为方法参数类型，value为方法
     */
    private Map<Class<?>, Method> getValueOfMethodMap(Class<?> enumClass) {
        return VALUE_METHOD_CACHE.computeIfAbsent(enumClass,
                k -> Arrays.stream(k.getMethods())
                        .filter(ModifierUtil::isStatic)
                        .filter(m -> m.getReturnType() == enumClass)
                        .filter(m -> m.getParameterCount() == 1)
                        .filter(m -> !"valueOf".equals(m.getName()))
                        .collect(Collectors.toMap(m -> m.getParameterTypes()[0], m -> m)));
        
    }
}
